using System;
using System.Collections;
using System.Collections.Specialized;
using System.IO;

namespace Copperline.Escriba
{
	/// <summary>
	/// Base class for most concrete loggers
	/// </summary>
	public abstract class LoggerBase : ILogger
	{
		private IFormatter formatter;
		/// <summary>
		/// Sets or gets the formatter used by this logger.
		/// </summary>
		public IFormatter Formatter
		{
			get { return formatter; }
			set { formatter = value; }
		}

		private IFilter filter;
		/// <summary>
		/// Sets or gets the filter used by this logger
		/// </summary>
		public IFilter Filter
		{
			get { return filter; }
			set { filter = value; }
		}

		private EscribaException escribaException;
		/// <summary>
		/// Stores a description of the last nontrivial error,
		/// especially an error resulting in setting <c>Fault</c> to <c>true</c>.
		/// <seealso cref="LoggerBase.Fault"/>
		/// </summary>
		public EscribaException LastError
		{
			get { return this.escribaException; }
			set { this.escribaException = value; }
		}

		/// <summary>
		/// Default constructor for <c>LoggerBase</c>.
		/// </summary>
		/// <remarks><c>LoggerBase</c> implements <c>Fault</c>, <c>Enabled</c>,
		/// and <c>LastError</c>. It also introduces enumerated types for
		/// severity and triggering.</remarks>
		public LoggerBase()
		{
			this.bFault = false;
			this.bEnabled = false;
			this.filter = null;
			this.formatter = null;
			this.escribaException = null;
			this.sName = String.Empty;
		}

		/// <summary>
		/// Creates an instance of the <c>LoggerBase</c> class
		/// </summary>
		/// <param name="formatter">the formatter to use</param>
		/// <param name="filter">the filter to use</param>
		/// <param name="name">the name to give this logger</param>
		public LoggerBase(IFormatter formatter, IFilter filter, string name)
		{
			this.bFault = false;
			this.bEnabled = true;
			this.filter = filter;
			this.formatter = formatter;
			this.escribaException = null;
			this.sName = name;
		}

		private bool bFault;
		/// <summary>
		/// Setting <c>Fault</c> to <c>true</c> takes this logger offline.
		/// <seealso cref="LoggerBase.LastError"/>
		/// </summary>
		/// <remarks>Applications should not set <c>Fault</c>. The usual way
		/// to suspend logging temporarily is to set <c>Enabled</c> to
		/// <c>false</c>. <c>Fault</c> is intended to flag serious internal
		/// errors (disk full, database disconnected, etc).
		/// </remarks>
		public virtual bool Fault
		{
			get { return this.bFault; }
			set { this.bFault = value; }
		}

		private bool bEnabled;
		/// <summary>
		/// Setting <c>Enabled</c> to <c>false</c> suspends logging.
		/// Setting <c>Enabled</c> to <c>true</c> resumes logging.
		/// <seealso cref="LoggerBase.Fault"/>
		/// </summary>
		/// <remarks>An application suspends and resumes logging temporarily by setting
		/// this property.</remarks>
		public virtual bool Enabled
		{
			get { return this.bEnabled; }
			set { this.bEnabled = value; }
		}

		private string sName;
		/// <summary>
		/// Sets or gets the application-specific name for <c>this</c> logger
		/// </summary>
		/// <remarks>Log managers may aggregate several loggers. An application provides a convenient
		/// name to distinguish one log from another, ie, "eMail logger"
		/// </remarks>
		public virtual string Name
		{
			get { return this.sName; }
			set { this.sName = value; }
		}

		/// <summary>
		/// Writes a string to the logger's data store.
		/// </summary>
		/// <remarks>For convenience in creating derived classes, this
		/// implementation forwards the logging request to another overload
		/// of <c>Write</c>. Authors of derived classes need to implement just
		/// <c>Write(ICollection logEntryCollection)</c> to get their classes
		/// running.</remarks>
		/// <param name="sEntry">The message to write, assigned a priority of ESeverity.debug</param>
		public virtual void Write( string sEntry )
		{
			if( this.Enabled && !this.Fault )
				Write( new LogEntry( sEntry, ESeverity.debug ) );
		}

		/// <summary>
		/// Writes an instance of <c>LogEntry</c> to the logger's data store.
		/// <seealso cref="LogEntry"/>
		/// </summary>
		/// <remarks>For convenience in creating derived classes, the
		/// implementation forwards the logging request to an overload
		/// of <c>Write</c>. Authors of derived classes need to implement just
		/// <c>Write(ICollection logEntryCollection)</c> to get their classes
		/// running.</remarks>
		/// <param name="logEntry">The log entry to write</param>
		public abstract void Write( LogEntry logEntry );

		/// <summary>
		/// Writes a <c>LogEntry</c> object to a <c>TextWriter</c> stream.
		/// </summary>
		/// <param name="sw">The stream to write to.</param>
		/// <param name="le">The <c>LogEntry</c> object to write.</param>
		internal protected void Append( TextWriter sw, LogEntry le )
		{
			if( le == null || sw == null )
				return;

			if( !Filter.HasRequiredSeverity( le.Severity ) )
				return;

			try
			{
				string[] asFormatted = Formatter.Format( le );
				for( int i=0; i<asFormatted.Length; i++ )
					sw.WriteLine( asFormatted[i] );
			}
			catch( Exception ex )
			{
				Fault = true;
				LastError = new EscribaException("Append failed to format log entry", ex );
			}
		}
	}
}
